/**
*    Copyright (C) 2008  Chase Kernan 
*    chase.kernan@gmail.com
*
*    This program is free software: you can redistribute it and/or modify
*    it under the terms of the GNU General Public License as published by
*    the Free Software Foundation, either version 3 of the License, or
*    (at your option) any later version.
*
*    This program is distributed in the hope that it will be useful,
*    but WITHOUT ANY WARRANTY; without even the implied warranty of
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*    GNU General Public License for more details.
*
*    You should have received a copy of the GNU General Public License
*    along with this program.  If not, see <http://www.gnu.org/licenses/>.
* 
**/

package com.chasekernan.hxnova.core.races;

import haxe.xml.Check;
import com.chasekernan.hxnova.core.races.TechLevel;

/**
    Each tech field can have a higher, normal, or lower cost than usual.
**/
enum Costs {
    More;
    Normal;
    Less;
}

/**
    Research costs holds the cost of researching each field for a race. It also
    has the basic tech types.
**/
class ResearchCosts {
    
    /**
        The basic tech fields that can be researched (ie energy, weapons, etc).
    **/
    public static var TECH_TYPES : Array<String> = ["energy", "weapons", 
            "propulsion", "construction", "electronics", "biotechnology"];
            
    /**
        The multiplier if a field costs more than average.
    **/
    public static var COST_MORE : Float = 1.75;
    
    /**
        The multiplier if a field costs the same (always 1 but can be used to 
        adjust tech costs overall.
    **/
    public static var COST_NORMAL : Float = 1;
    
    /**
        The multiplier used if a field costs less than normal.
    **/
    public static var COST_LESS : Float = 0.5;
    
    public var values : Array<Costs>;
    
    /**
        Sets all costs to normal values.
    **/
    public function new () {
        values = new Array<Costs>();
        for(i in 0...TECH_TYPES.length) values.push(Normal);
    }
    
    /**
        Returns the cost of a specific field (i.e. More, Normal, or Less).
        Throws an error if the given type is not a tech field.
    **/
    public function getCost(type : String) : Costs {
        //find value
        for(i in 0...TECH_TYPES.length) {
            if(TECH_TYPES[i] == type) return values[i];
        }
        
        //if here throw new Error
        throw "No such tech field: "+type;
    }
    
    /**
        Returns the multiplier for the given enum cost.
        Cost.More -> COST_MORE
        Cost.Normal -> COST_NORMAL
        etc
    **/
    public static function getValue(cost : Costs) : Float {
        return switch(cost) {
            case More: COST_MORE;
            case Normal: COST_NORMAL;
            case Less: COST_LESS;
        }
    }
    
    /**
        Returns the enum value of a specified cost multiplier.
        COST_MORE -> Cost.More
        etc...
        Throws an error if multiplier does not match Normal, More, or Less.
    **/
    public static function getCostFromValue(value : Float) : Costs {
        switch(value) {
            case COST_MORE: return More;
            case COST_NORMAL: return Normal;
            case COST_LESS: return Less;
            default: throw "No such cost multiplier: " + value;
        }
        
        //need this here
        return Normal;
    }
    
    public static var XML_TITLE = "research_costs";
    
    public static function getXmlRules() : Rule {
        var nameArray : Array<Rule> = new Array<Rule>();
        for(i in TECH_TYPES) nameArray.push(Rule.RNode(i, [Att('value')]));
        
        return Rule.RNode(XML_TITLE, [], Rule.RList(nameArray, false));
    }
    
    /**
        Sets the cost associated with a tech field.
        Throws an error if the tech type does not exist.
    **/
    public function setValue(type : String, cost : Costs) {
        if (cost == null) throw "Cost cannot be null.";
        
        for(i in 0...TECH_TYPES.length) {
            if(TECH_TYPES[i] == type) {
                values[i] = cost;
                return;
            }
        }
        //if here no such value
        throw "No such tech field: " + type;
    }
    
    /**
        Returns the enum specified by name. ie:
        "More" -> More
        "Normal" -> Normal
        etc...
        Throws an error if there is no such enum by that name.
    **/
    public static function getCostByName(name : String) : Costs {
        return switch(name) {
            case "More": More;
            case "Less": Less;
            case "Normal": Normal;
            
            default: throw "No such cost multiplier: " + name;
        }
    }
    
    /**
        Returns the number of tech fields where the cost is less than normal.
    **/
    public function getFieldsCheap() : Int {
        var fields = 0;
        for(i in values) {
            switch(i) {
                case Less: fields ++;
                default: //do nothing
            }
        }
        return fields;
    }
    
    /**
        Returns the number of tech fields where the cost is more than normal.
    **/
    public function getFieldsExpensive() : Int {
        var fields = 0;
        for(i in values) {
            switch(i) {
                case More: fields ++;
                default: //do nothing
            }
        }
        return fields;
    }
    
    /**
        Writes a research costs set to xml.
        Sample XML:
            <research_costs>
                <energy value="1">
                <weapons value="1.75">
                <construction value="0.5">
                <propulsion value="1">
                ...
            </reasearch_costs>
    **/
    public static function writeToXml(rc : ResearchCosts) : Xml {
        var xml : Xml = Xml.parse('<' + XML_TITLE + '></' + XML_TITLE + '>')
                .firstChild();
        
        for(i in TECH_TYPES) {
            var childXml = Xml.createElement(i);
            childXml.set('value', Std.string(getValue(rc.getCost(i))));
            xml.addChild(childXml);
        }
        //check it 
        Check.checkNode(xml, getXmlRules());
        return xml;
    }
    
    /**
        Reads a research costs set from xml.
        Throws an error if there are any mistakes.
    **/
    public static function readFromXml(xml : Xml) : ResearchCosts {
        try {
            var rc = new ResearchCosts();
            //check type
            Check.checkNode(xml, getXmlRules());
            
            for(i in xml.elements()) {
                rc.setValue(i.nodeName, getCostFromValue(Std.parseFloat(
                        i.get('value'))));
            }
            
            return rc;
        } catch (e : Dynamic) {
            throw "Bad research cost xml file: " + e;
        }
    }
    
    public function iterator () {
        return values.iterator();
    }
    
    /**
        Returns true if there is such a tech type.
    **/
    public static function checkType(type : String) : Bool {
        for (t in TECH_TYPES) {
            if (t == type) return true;
        }
        return false;
    }
}


//TODO: Change ResearchCosts#setValue -> setCost

#if unit
import haxe.unit.TestCase;

class ResearchCostsTest extends haxe.unit.TestCase {
    public var rs : ResearchCosts;
    
    public function testTechLevel() {
        rs = new ResearchCosts();
        
        for (techType in ResearchCosts.TECH_TYPES) {
            switch(rs.getCost(techType)) {
                case Normal: assertTrue(true);
                default: assertTrue(false);
            }
        }
        
        rs.setValue(ResearchCosts.TECH_TYPES[0], Less);
        switch(rs.getCost(ResearchCosts.TECH_TYPES[0])) {
            case Less: assertTrue(true);
            default: assertTrue(false);
        }
        
        try {
            rs.setValue(ResearchCosts.TECH_TYPES[1], 
                          null);
            assertTrue(false);
        } catch (e : Dynamic) {
            assertTrue(true);
        }
        
        try {
            rs.getCost("gibberish");
            assertTrue(false);
        } catch (e : Dynamic) {
            assertTrue(true);
        }
        
        rs.setValue(ResearchCosts.TECH_TYPES[1], More);
        rs.setValue(ResearchCosts.TECH_TYPES[2], Less);
        
        assertEquals(rs.getFieldsCheap(), 2);
        assertEquals(rs.getFieldsExpensive(), 1);
        
        assertFalse(ResearchCosts.checkType("gibberish"));
        assertTrue(ResearchCosts.checkType(ResearchCosts.TECH_TYPES[2]));
        
        switch(ResearchCosts.getCostByName("Normal")) {
            case Normal: assertTrue(true);
            default: assertTrue(false);
        }
        
        switch(ResearchCosts.getCostFromValue(ResearchCosts.COST_MORE)) {
            case More: assertTrue(true);
            default: assertTrue(false);
        }
        
        assertEquals(ResearchCosts.COST_LESS, ResearchCosts.getValue(Less));
    }
    
    public function testResearchCostsXml() {
        rs = new ResearchCosts();
        rs.setValue(ResearchCosts.TECH_TYPES[0], More);
        rs.setValue(ResearchCosts.TECH_TYPES[1], Less);
        
        var xml = ResearchCosts.writeToXml(rs);
        for (element in xml.elements()) {
            assertEquals(ResearchCosts.getValue(rs.getCost(element.nodeName)), 
                    Std.parseFloat(element.get("value")));
        }
        
        var readValues = ResearchCosts.readFromXml(xml);
        assertEquals(rs.getFieldsCheap(), readValues.getFieldsCheap());
        assertEquals(rs.getFieldsExpensive(), readValues.getFieldsExpensive());
    }
}

#end